<?php
require_once 'Core.php';
define('CORE_TIMEOUT',60);
/**
 * Ticker class, simulate clock.
 *
 *  When the core is running the core table have the column 'running' setted true.
 *
 *	If the core is running for a time greater than the CORE_TIMEOUT constant
 *	it will try run again from the point where it stopped and set a new timestamp.
 *
 *	In a future I may try to run multiple cores simultaneously.
 * @static
 * @package meta
 * @author alcaitiff
 */
class Ticker
{

	private static $lastTick=null;
    private static $currentTick=null;
    private static $undoneTicks=null;
    private static $tickSize=null;
    private static $running=false;
    /**
     * If core is not running and the current tick is greater than last tick + TICK_SIZE
     * run the core until core to be up to date
     * @return void
     * @static
     */
	static public function tick(){
		self::retrieveCoreData();
		self::calcUndoneAndCurrentTick();
		if(self::changeCoreState(1)){
			if(self::$undoneTicks>0){
				MessageSystem::addDebugMessage('Ticker calling the core for '.self::$undoneTicks.' ticks');
				Core::run(self::$undoneTicks,self::$lastTick,self::$tickSize);
			}else{
				MessageSystem::addDebugMessage('Core is up to date.');
				//MessageSystem::addDebugMessage('Last tick: '.self::$lastTick.' Current tick: '.self::$currentTick.' Tick size: '.self::$tickSize);
			}
			self::changeCoreState(0,self::$currentTick);
		}else{
			MessageSystem::addDebugMessage('Core running');
		}
				
	}
    /**
     * Retrieve current tick
     * @return int
     * @static
     */
	static public function getCurrentTick(){
		if(self::$currentTick===null){
			self::retrieveCoreData();
			self::calcUndoneAndCurrentTick();
		}
		return self::$currentTick;
	}
    /**
     * Retrieve tick size
     * @return int
     * @static
     */
	static public function getTickSize(){
		if(self::$tickSize===null){
			self::retrieveCoreData();
		}
		return self::$tickSize;
	}


	/**
     * Calculate undone ticks and discover the current tick
     * @static
     * @access private
     * @return void
     */
	static private function calcUndoneAndCurrentTick(){
		if(self::$undoneTicks===null or self::$currentTick===null){
			$dateTime=new DateTime('NOW');
			$currentTime=$dateTime->format('U');
			$difference=$currentTime-self::$lastTick;
			self::$undoneTicks=floor($difference/self::$tickSize);
			self::$currentTick=self::$lastTick+(self::$undoneTicks*self::$tickSize);
		}
	}
	
    //***********************************************************************************************************
    //					DATABASE METHODS
    //***********************************************************************************************************

    /**
     * Retrieve tick size and last tick
     * @return void
     * @static
     */
	static private function retrieveCoreData(){
		if(self::$tickSize===null or self::$lastTick===null){
			$dbo=new DB();
			$db=$dbo->db;
			$stmt = $db->prepare(	'SELECT tick_size, UNIX_TIMESTAMP(timestamp) '.
									'FROM core '.
									'LIMIT 1'
										);
			$stmt->bind_result(self::$tickSize,self::$lastTick);									
		    $stmt->execute();
   			$result=$stmt->fetch();
			$stmt->close(); 
		}
	}

    /**
     * Change the core state return true if success
     * @param boolean $state
     * @param int $currentTick
     * @return boolean
     * @static
     */
	static private function changeCoreState($state,$currentTick=null){
		$aux=($state)?0:1;	
		$queryAux=($currentTick===null)?'':', timestamp = FROM_UNIXTIME(?) ';
		$dbo=new DB();
		$db=$dbo->db;
		$stmt = $db->prepare(	'UPDATE core '.
								'SET running = ? '.
								$queryAux.
								'WHERE running = ?'
									);
		if($currentTick===null){
			$stmt->bind_param('ii',$state,$aux);									
		}else{
			$stmt->bind_param('iii',$state,$currentTick,$aux);									
		}
	    $stmt->execute();
	    
		if($stmt->affected_rows>0){
			self::$running=$state;
		}elseif($state==1){
			//This code verify the core timeout
			//trying to avoid dead locks
			//because of this the core must try to work properly in concurrent state
			$stmt->close();
			$stmt = $db->prepare(	'UPDATE core '.
									'SET timestamp = FROM_UNIXTIME(?) '.
									'WHERE running = 1 and timestamp<FROM_UNIXTIME(?)'
									);
			$dateObject= new DateTime('NOW');
			$coreTimeOut=(string)($dateObject->format('U')-CORE_TIMEOUT);
			$stmt->bind_param('ss',self::$currentTick,$coreTimeOut);
			$stmt->execute();
			if($stmt->affected_rows>0){
				self::$running=$state;
			}
		}
		$stmt->close();
		return self::$running;
	}
}